<?php

namespace Ansonv\Yii2Pingandirectconn\Lib;

use Yii;
use yii\base\Exception;


abstract class Base
{
    protected $header_data;
    protected $header_format;
    protected $api_group;
    protected $api_mapping;
    protected $api_type;
    protected $api_id = '';
    public $AccountNo;

//    protected $AccountNo;
    //接口结构
    protected $api_data;

    protected function __construct()
    {
        $this->init();
    }

    private function init()
    {
        $config = include(__DIR__ . "/../config.php");
        $this->header_data = $config['apiHeaders'];
        $this->header_format = $config['apiHeadersFormat'];
        $this->api_group = $config['apiGroup'];
        $this->api_mapping = $config['apiMapping'];
        $this->api_data = $config['apiData'];
    }

    abstract public function callApi($api_id, $params = []);

    protected function getMapping($name)
    {
        if (isset($this->api_mapping[$this->api_type][$name])) {
            return $this->api_mapping[$this->api_type][$name];
        }
        return false;
    }

    protected function setApi($api_id)
    {
        $this->api_id = $api_id;
        foreach ($this->api_group as $k => $v) {
            if (in_array($api_id, $v)) {
                $this->api_type = $k;
                break;
            }
        }

        if (!$this->api_type) {
            $this->api_type = 'nothing';
        }
        return $this;
    }

    public function setAccountNo($account_no = '')
    {
        if ($account_no != '') {
            $this->AccountNo = $account_no;
        }
        $key = $this->getMapping('account_no');
        $this->setHeaderData([$key => $this->AccountNo]);
        return $this;
    }


    protected function setApiId()
    {
        $api_id = $this->api_id;
        $api_id = strval($api_id);
        $len = strlen($api_id);
        $key = $this->getMapping('api_id');
        $format = $this->header_format[$this->api_type][$key];
        for ($i = $len; $i < $format['digit']; $i++) {
            $api_id .= " ";
        }
        $this->setHeaderData([$key => $api_id]);
        return $this;
    }

    protected function setSerialNumber()
    {
        $tail = explode(' ', microtime());
        $num = date("YmdHis") . substr($tail[0], 2, 6);
        $len = strlen($num);
        $key = $this->getMapping('ThirdVoucher');
        $format = $this->header_format[$this->api_type][$key];
        for ($i = $len; $i < $format['digit']; $i++) {
            $num .= " ";
        }
        $this->setHeaderData([$key => $num]);
        return $this;
    }

    protected function setXmlLength($length)
    {
        $length = strval($length);
        $len = strlen($length);
        $key = $this->getMapping('xml_length');
        $format = $this->header_format[$this->api_type][$key];
        for ($i = intval($len); $i < $format['digit']; $i++) {
            $length = "0" . $length;
        }
        $this->setHeaderData([$key => $length]);
    }

    protected function setHeaderData($setData)
    {
        foreach ($setData as $k => $v) {
            if (isset($this->header_data[$this->api_type][$k])) {
                $this->header_data[$this->api_type][$k] = $v;
            }
        }
        return $this;
    }

    protected function getHeaderData()
    {
//        dd($this->header_data[$this->api_type]);
        return implode("", $this->header_data[$this->api_type]);
    }

    protected function getApiData()
    {
        $all_data = $this->api_data[$this->api_type];
        if ($this->api_id != '' && isset($all_data[$this->api_id])) {
            return $all_data[$this->api_id];
        } else {
            return $all_data;
        }
    }

    protected function pickupData($params)
    {
        //组装报文体
//        $api_data = $this->getApiData();
//        $api_data = array_merge($api_data, $params);
        $api_data = $params;
        $xml = $this->makeXml($api_data);
//        dd($xml);
        $header = $this->setAccountNo()->setApiId()->setSerialNumber()->getHeaderData();
        return $header . $xml;
    }

    protected function makeXml($api_data)
    {
        $xml = $this->arrayToXml($api_data);
//                header("Content-Type: text/html; charset=GBK");

//        dd($xml);
        $this->setXmlLength(strlen($xml));
        return $xml;
    }

    protected function arrayToXml($data)
    {
        if (!is_array($data) || count($data) <= 0) {
            return false;
        }
        $xml = '<?xml version="1.0" encoding="GBK" ?><Result>';
        foreach ($data as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $this->charsetToGBK($val) . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . ">" . $this->charsetToGBK($val) . "</" . $key . ">";
                //示例报文里面无需特殊处理
//$xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</Result>";
        return $xml;
    }

    protected function charsetToGBK($mixed)
    {
        if (is_array($mixed)) {
            foreach ($mixed as $k => $v) {
                if (is_array($v)) {
//                    dd($v);
                    $mixed[$k] = $this->charsetToGBK($v);
                } else {
                    $encode = mb_detect_encoding($v, array('ASCII', 'UTF-8', 'GB2312', 'GBK', 'BIG5'));
                    if ($encode == 'UTF-8') {
                        $mixed[$k] = iconv('UTF-8', 'GBK', $v);
                    }
                }
            }
        } else {

            $encode = mb_detect_encoding($mixed, array('ASCII', 'UTF-8', 'GB2312', 'GBK', 'BIG5'));
//            dd($encode);
//var_dump($encode);
            if ($encode == 'UTF-8') {
                $mixed = iconv('UTF-8', 'GBK', $mixed);
            } else {
                $mixed = mb_convert_encoding($mixed, 'GB2312');
            }
//            if ($encode == 'ASCII') {
//                $mixed = iconv('ASCII', 'GBK', $mixed);
//            }
//            dd($mixed);
//            dd(mb_detect_encoding($mixed, array('ASCII', 'UTF-8', 'GB2312', 'GBK', 'BIG5')));
        }
        return trim($mixed);
    }

    protected function runSocket($context)
    {
//        header("Content-Type: text/html; charset=GBK");
//        dd($context);
        $b2bi_config = Yii::$app->params['pinganBank']['b2bic'];
        $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        if (!$socket) {
            throw new Exception("创建Socket连接失败:" . socket_strerror(socket_last_error()));
//            Yii::app()->exception_watcher->notify_exception(new Exception("socket_create() 失败:" . socket_strerror($socket)));
            socket_close($socket);
        }
        $result = socket_connect($socket, $b2bi_config['ip_address'], $b2bi_config['port']);
        if (!$result) {
            throw new Exception("B2Bi连接失败:" . socket_strerror(socket_last_error()));
            socket_close($socket);
        }
//        $context = $this->charsetToGBK($context);
//                dd($context);

        //发送报文
        if (!socket_write($socket, $context, strlen($context))) {
            throw new Exception("发送报文失败" . socket_strerror(socket_last_error()));
            socket_close($socket);

        }
        //4096
        $res = '';
//        dd($res);
        while ($out = socket_read($socket, 4096)) {
//            echo "接收服务器回传信息成功！\n";
//            echo "接受的内容为:", $out;
            $res .= $out;
        }

        if (strlen($res) <= 100) {
            throw new Exception("返回数据无效：" . iconv('GB2312', 'UTF-8', $res));
        }
//                socket_close($socket);
//        header("Content-Type: text/html; charset=GBK");

//        dd($res);
        if ($this->hasPackage($res)) {
            while ($out = socket_read($socket, 4096)) {
//            echo "接收服务器回传信息成功！\n";
//            echo "接受的内容为:", $out;
                $res .= $out;
            }
        }
        if ($this->hasPackage($res)) {
            while ($out = socket_read($socket, 4096)) {
//            echo "接收服务器回传信息成功！\n";
//            echo "接受的内容为:", $out;
                $res .= $out;
            }
        }

        socket_close($socket);

        return $res;
    }


    protected function extractHeader($context)
    {
        $header = substr($context, 0, 222);
        $format = $this->header_format[$this->api_type];
        $new_header = [];
        $num = 0;

        foreach ($format as $k => $v) {
            $new_header[$k] = iconv('GB2312', 'UTF-8', substr($header, 0 + $num, $v['digit']));
            $num += $v['digit'];
        }
//        $new_header = json_encode($new_header);
//        header("Content-Type: text/html; charset=GBK");
//        dd($new_header);
        return $new_header;
    }

    protected function extractXml($context)
    {
        $xml = substr($context, 222);
        $xml = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
        $xml = json_encode($xml);
        $xml = json_decode($xml, 1);
        return $xml;
    }

    private function hasPackage($context)
    {
        $format = $this->header_format[$this->api_type];
        $key = $this->getMapping('xml_length');
        $start_num = 0;
        for ($i = 1; $i < $key; $i++) {
            $start_num = $start_num + $format[$i]["digit"];
        }
        $length = substr($context, $start_num, $format[$key]['digit']);
        if ((strlen($context) - 222) < intval($length)) {
            return true;
        } else {
            return false;
        }
//        dd($length);
    }


    protected function unPackContext($context)
    {
        $result_xml = $this->extractXml($context);
        $result_header = $this->extractHeader($context);
        $code = $result_header[$this->api_mapping[$this->api_type]['code']];
        $message = $result_header[$this->api_mapping[$this->api_type]['message']];
        return ['header' => $result_header, 'data' => $result_xml, 'code' => trim($code), 'message' => trim(trim($message),":")];
    }


}